using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using System.Reflection;
using System.IO;
using System.Xml.Serialization;
using System.Xml;


namespace www.serviciipeweb.ro.iafblog.ExportHierarchical
{
    #region generic interface

    /// <summary>
    /// Inode is the specific interface for a node to be serialized
    /// </summary>
    public interface INode
    {

        /// <summary>
        /// Gets or sets the level. - you can verify agains the maximum number of levels
        /// </summary>
        /// <value>The level.</value>
        int Level { get; set; }

        /// <summary>
        /// Gets or sets the display name.
        /// </summary>
        /// <value>The name.</value>
        string Name { get;set;}
        /// <summary>
        /// Gets or sets the unique ID - usefull for achor, references etc.
        /// </summary>
        /// <value>The unique ID.</value>
        string UniqueID { get; set; }
        /// <summary>
        /// Gets the type of the object
        /// </summary>
        /// <value>The type of the object</value>
        Type oType { get; }
        /// <summary>
        /// Adds the property to the node to be displayed
        /// </summary>
        /// <param name="Key">The key = property name</param>
        void AddProperty(string Key);
        /// <summary>
        /// Adds the objects to the tree
        /// </summary>
        /// <param name="NamePropObject">The prop name of the object.</param>
        /// <param name="DisplayName">The display name </param>
        /// <param name="Key">The key</param>
        /// <returns></returns>
        HashTableAddRecursiveObjects AddObjects(string NamePropObject, string DisplayName, string Key);

        /// <summary>
        /// Adds the objects to the tree
        /// </summary>
        /// <param name="NamePropObject">The prop name of the object.</param>
        /// <param name="DisplayName">The display name of the object</param>
        /// <param name="Key">The key - must return Enumerable</param>
        /// <param name="Exclude">Exclude some values on condition</param>
        /// <returns></returns>
        HashTableAddRecursiveObjects AddObjects(string NamePropObject, string DisplayName, string Key, Func<object, bool> Exclude);



        /// <summary>
        /// Adds directly teh subnodes
        /// </summary>
        /// <param name="Key">The key</param>
        /// <param name="val">The value to be added</param>
        void AddObject(string Key, HashTableAddRecursiveObjects val);

        /// <summary>
        /// Gets the properties to be displayed
        /// </summary>        
        HashTableAddRecursiveProperties Properties { get; }


        /// <summary>
        /// Gets the sub nodes to be displayed
        /// </summary>        
        HashTableAddRecursiveCollection SubNodes { get; }


    }
    #endregion
    #region main objects
    /// <summary>
    /// One implementation of the INode
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class ST_Node<T> : INode
        where T : class
    {

        #region fields
            private static Dictionary<string, MethodInfo> methodsCachePrimary;
            private T m_O;
        #endregion
        #region constructors
        /// <summary>
        /// Initializes a new instance of the <see cref="ST_Node&lt;T&gt;"/> class.
        /// </summary>
        /// <param name="o">The object to be displayed - helpfull to find its properties</param>
        public ST_Node(T o)
            : this(o, "Name", "ID")
        {

        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ST_Node&lt;T&gt;"/> class.
        /// </summary>
        /// <param name="o">The object to be displayed with properties</param>
        /// <param name="NameProp">The name property that acts as display name</param>
        /// <param name="DefaultID">The property name of the default ID</param>
        public ST_Node(T o, string NameProp, string DefaultID)
        {
            Level = 1;
            Properties = new HashTableAddRecursiveProperties();
            SubNodes = new HashTableAddRecursiveCollection();
            m_O = o;
            oType = typeof(T);
            if (NameProp != null)
            {
                PropertyInfo pi = oType.GetProperty(NameProp, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
                this.Name = pi.GetGetMethod().Invoke(o, null).ToString();
            }
            if (DefaultID != null)
            {
                PropertyInfo pi = oType.GetProperty(DefaultID, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
                this.UniqueID = oType.FullName + "_" + pi.GetGetMethod().Invoke(o, null).ToString();
            }

        }
        

        /// <summary>
        /// Initialize caching for methods...
        /// </summary>
        static ST_Node()
        {
            methodsCachePrimary = new Dictionary<string, MethodInfo>();
        }
        #endregion

        /// <summary>
        /// Returns a <see cref="T:System.String"/> that represents the current value<see cref="T:System.Object"/>.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.String"/> that represents the current id and display name<see cref="T:System.Object"/>.
        /// </returns>
        public override string ToString()
        {
            return Name + "(" + this.UniqueID + ")";
        }

        /// <summary>
        /// type of the object
        /// </summary>
        

        #region INode implementation
        /// <summary>
        /// see <see cref="INODE"/>
        /// </summary>
        public Type oType { get; internal set; }
        /// <summary>
        /// see <see cref="INODE"/>
        /// </summary>
        public int Level { get; set; }
        /// <summary>
        /// see <see cref="INODE"/>
        /// </summary>
        public string Name { get; set; }
        /// <summary>
        /// see <see cref="INODE"/>
        /// </summary>
        public string UniqueID { get; set; }

        /// <summary>
        /// see <see cref="INODE"/>
        /// </summary>
        public HashTableAddRecursiveProperties Properties { get; private set; }
        /// <summary>
        /// see <see cref="INODE"/>
        /// </summary>
        public HashTableAddRecursiveCollection SubNodes { get; private set; }

        /// <summary>
        /// see <see cref="INODE"/>
        /// </summary>
        /// <param name="Key"></param>
        /// <param name="val"></param>
        public void AddObject(string Key, HashTableAddRecursiveObjects val)
        {
            SubNodes.Add(Key, val);
        }
        /// <summary>
        /// see <see cref="INODE"/>
        /// </summary>
        /// <param name="NamePropObject"></param>
        /// <param name="DisplayName"></param>
        /// <param name="Key"></param>
        /// <returns></returns>
        public HashTableAddRecursiveObjects AddObjects(string NamePropObject, string DisplayName, string Key)
        {
            return AddObjects(NamePropObject, DisplayName, Key, (o) => false);

        }
        /// <summary>
        /// see <see cref="INODE"/>
        /// </summary>
        /// <param name="NamePropObject"></param>
        /// <param name="DisplayName"></param>
        /// <param name="Key"></param>
        /// <param name="Exclude"></param>
        /// <returns></returns>
        public HashTableAddRecursiveObjects AddObjects(string NamePropObject, string DisplayName, string Key, Func<object, bool> Exclude)
        {
            HashTableAddRecursiveObjects ht = new HashTableAddRecursiveObjects();
            IEnumerable o = PropertyMethodValueRecurse(NamePropObject, this.m_O) as IEnumerable;
            int NextLevel = this.Level + 1;
            int i = 0;
            foreach (object oLoop in o)
            {

                if (Exclude != null && Exclude(oLoop))
                    continue;

                PropertyInfo pi = oLoop.GetType().GetProperty(Key);
                INode node = GenReflectNode.CreateExporter(oLoop, DisplayName, Key);
                node.Level = NextLevel;
                ht.Add(pi.GetGetMethod().Invoke(oLoop, null).ToString(), node);


            }

            AddObject(NamePropObject.Replace("()", ""), ht);
            return ht;
        }
        /// <summary>
        /// see <see cref="INODE"/>
        /// </summary>
        /// <param name="Key"></param>
        public void AddProperty(string Key)
        {
            Properties.Add(Key, PropertyValue(Key));
        }
        #endregion
        #region public methods

        /// <summary>
        /// Gets a value indicating whether exist sub nodes or properties.
        /// </summary>        
        public bool ExistSubNodesOrProperties
        {
            get
            {
                return ExistSubNodes || ExistProperties;
            }
        }

        /// <summary>
        /// Gets a value indicating whether exist sub nodes.
        /// </summary>        
        public bool ExistSubNodes
        {
            get
            {
                return SubNodes.Count > 0;
            }
        }
        /// <summary>
        /// Gets a value indicating whether exist properties.
        /// </summary>
        public bool ExistProperties
        {
            get
            {
                return Properties.Count > 0;
            }
        }

        /// <summary>
        /// Returns the value of Key property or method
        /// uses methodsCachePrimary 
        /// </summary>
        /// <param name="Key">The name of prop or method (with Paranthesis!).</param>
        /// <param name="o">The object to find the method and value</param>
        /// <returns></returns>
        internal object PropertyMethodValueRecurse(string Key, object o)
        {
            MethodInfo mi = null;
            string FirstPart = Key;
            string SecondPart = null;
            if (Key.IndexOf(".") > 0)
            {
                FirstPart = Key.Substring(0, Key.IndexOf("."));
                SecondPart = Key.Substring(Key.IndexOf(".") + 1);

            }


            if (methodsCachePrimary.ContainsKey(FirstPart))
            {
                mi = methodsCachePrimary[FirstPart];
            }
            else
            {



                if (Key.IndexOf("()") > 0)
                {
                    Key = Key.Replace("()", "");
                    mi = o.GetType().GetMethod(Key, new Type[0]); //method with no parameters...
                }
                else
                {
                    PropertyInfo pi = o.GetType().GetProperty(FirstPart, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
                    if (pi.GetGetMethod() == null)
                    {
                        throw new NotImplementedException("property " + FirstPart + " does not exists");
                    }
                    mi = pi.GetGetMethod();
                }

                methodsCachePrimary.Add(FirstPart, mi);
            }

            object ret = mi.Invoke(o, null);

            if (SecondPart != null)
                return PropertyMethodValueRecurse(SecondPart, ret);
            else
                return ret;


        }

        /// <summary>
        /// returns the value of the property
        /// Virtual for can be retrieved on other implementations
        /// </summary>
        /// <param name="Key">The key</param>
        /// <returns></returns>
        public virtual object PropertyValue(string Key)
        {
            return PropertyMethodValueRecurse(Key, m_O);

        }

        #endregion
        
    }
    
    #endregion


    public class ST_Hash<T, U> : Hashtable
        where U : class
        where T : ST_Node<U>
    {
        public ST_Hash(IEnumerable<T> i)
        {
            foreach (T o in i)
            {
                this.Add(o.UniqueID, o);
            }
        }
    }




    /// <summary>
    /// utilities for transforming to/from UTF8
    /// </summary>
    class utilitiesString
    {
        /// <summary>
        /// UTf8 byte array to string.
        /// </summary>
        /// <param name="characters">The characters.</param>
        /// <returns></returns>
        public static String UTF8ByteArrayToString(Byte[] bytes)
        {
            UTF8Encoding encoding = new UTF8Encoding();
            String constructedString = encoding.GetString(bytes);
            return (constructedString);
        }

        /// <summary>
        /// Strings to UTf8 byte array.
        /// </summary>
        /// <param name="pXmlString">The p XML string.</param>
        /// <returns></returns>
        public static Byte[] StringToUTF8ByteArray(String chars)
        {
            UTF8Encoding encoding = new UTF8Encoding();
            Byte[] byteArray = encoding.GetBytes(chars);
            return byteArray;
        }
    }

    #region recursive collections
    /// <summary>
    /// class for objects - recursive collection
    /// </summary>
    public class HashTableAddRecursiveCollection : HashTableAddRecursive<HashTableAddRecursiveObjects>
    {

    }


    /// <summary>
    /// class for each object
    /// </summary>
    public class HashTableAddRecursiveObjects : HashTableAddRecursive<INode>
    {
        public void AddPropertyToValues(string Name)
        {

            foreach (INode o in this.Values)
            {
                o.AddProperty(Name);
            }
        }
    }


    /// <summary>
    /// class for properties
    /// </summary>
    public class HashTableAddRecursiveProperties : HashTableAddRecursive<object>
    {

    }


    /// <summary>
    /// abstract class for tree adding
    /// </summary>    
    public abstract class HashTableAddRecursive<T> : Dictionary<string, T>
        where T : class
    {

        /// <summary>
        /// conversion to Hash Table
        ///required by String Template implementation
        /// </summary>
        /// <param name="t"></param>
        /// <returns></returns>
        public static implicit operator Hashtable(HashTableAddRecursive<T> t)
        {
            Hashtable ht = new Hashtable();
            foreach (string key in t.Keys)
            {
                ht.Add(key, t[key]);
            }
            return ht;
        }
    }

    #endregion

    #region not finished
    /// <summary>
    /// not finished class to can serialize the hierarchy of objects
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class HierarchySerializer<T>
        where T : class
    {

        public HierarchySerializer()
        {
            nodesList = new List<object>();
        }
        private void AddRecursive(INode i)
        {
            //object st=GenReflect.Create("ST_Exporter.ST_Node_Serializer", i, i.oType);
            //ST_Node_Serializer<T> st = new ST_Node_Serializer<T>(i);
            ST_Node_Serializer st = new ST_Node_Serializer(i);
            nodesList.Add(st);
            foreach (Hashtable ht in i.SubNodes.Values)
            {
                foreach (INode iloop in ht.Values)
                {
                    AddRecursive(iloop);
                    //assuming all subnodes of the same type
                    break;
                }
            }

        }
        public HierarchySerializer(ST_Node<T> initial)
            : this()
        {

            AddRecursive(initial);
        }
        //refactor

        private static XmlSerializer _xs;
        private static XmlSerializer xmls
        {
            get
            {
                if (_xs == null)
                {
                    _xs = new XmlSerializer(typeof(HierarchySerializer<T>), new Type[1] { typeof(ST_Node_Serializer) });

                }
                return _xs;
            }
        }


        public String Serialize()
        {

            MemoryStream memoryStream = new MemoryStream();
            XmlTextWriter xmlTextWriter = new XmlTextWriter(memoryStream, Encoding.UTF8);
            xmls.Serialize(xmlTextWriter, this);
            memoryStream = (MemoryStream)xmlTextWriter.BaseStream;
            return utilitiesString.UTF8ByteArrayToString(memoryStream.ToArray());

        }
        public static HierarchySerializer<T> DeserializeObject(String XML)
        {

            MemoryStream memoryStream = new MemoryStream(utilitiesString.StringToUTF8ByteArray(XML));
            XmlTextWriter xmlTextWriter = new XmlTextWriter(memoryStream, Encoding.UTF8);
            return xmls.Deserialize(memoryStream) as HierarchySerializer<T>;
        }
        List<object> nodesList;

        [XmlArray(ElementName = "Nodes")]
        public object[] Nodes
        {
            get
            {
                nodesList.TrimExcess();
                return nodesList.ToArray();
            }
            set
            {
                nodesList.Clear();
                nodesList.AddRange(value);
            }
        }
    }

    [Serializable]
    public class ST_Node_Serializer
    {

        private static XmlSerializer _xs;
        private static XmlSerializer xmls
        {
            get
            {
                if (_xs == null)
                {
                    _xs = new XmlSerializer(typeof(ST_Node_Serializer), new XmlRootAttribute("ST_Node_Serializer"));

                }
                return _xs;
            }
        }

        public String Serialize()
        {

            MemoryStream memoryStream = new MemoryStream();
            XmlTextWriter xmlTextWriter = new XmlTextWriter(memoryStream, Encoding.UTF8);
            xmls.Serialize(xmlTextWriter, this);
            memoryStream = (MemoryStream)xmlTextWriter.BaseStream;
            return utilitiesString.UTF8ByteArrayToString(memoryStream.ToArray());



        }
        public static ST_Node_Serializer DeserializeObject(String XML)
        {

            MemoryStream memoryStream = new MemoryStream(utilitiesString.StringToUTF8ByteArray(XML));
            XmlTextWriter xmlTextWriter = new XmlTextWriter(memoryStream, Encoding.UTF8);
            return xmls.Deserialize(memoryStream) as ST_Node_Serializer;
        }

        public ST_Node_Serializer()
        {
        }
        public ST_Node_Serializer(INode initial)
            : this()
        {
            NodeType = initial.GetType().FullName;
            foreach (string s in initial.Properties.Keys)
            {
                Properties += (s + ";");
            }

            foreach (string s in initial.SubNodes.Keys)
            {
                ObjectsProps += (s + ";");
            }

        }

        public string NodeType { get; set; }
        public string ObjectsProps { get;set;}

        public string Properties { get;set;}


    }
    #endregion
    

    

    
    /// <summary>
    /// Implemented after
    /// http://bartdesmet.net/blogs/bart/archive/2006/09/11/4410.aspx
    /// </summary>
    public class GenReflectNode
    {

        /// <summary>
        /// Creates the exporter of ST_NODE
        /// </summary>
        /// <param name="oLoop">The object to be searached for properties/tree nodes</param>
        /// <param name="NameProp">The property name - see constructor of ST_NODE</param>
        /// <param name="DefaultID">The ID of the node - see constructor of ST_NODE</param>
        /// <returns></returns>
        public static INode CreateExporter(object oLoop, string NameProp, string DefaultID)
        {
            //(T o,string NameProp, string DefaultID)
            string t = "ST_Exporter.ST_Node" + "`1";
            Type generic = Type.GetType(t).MakeGenericType(oLoop.GetType());
            return Activator.CreateInstance(generic, oLoop, NameProp, DefaultID) as INode;

        }
    }
    

}
